//
//  OosService.m
//  Oos4ios
//
//  Created by Pablo Gonzalez on 2/21/11.
//

#import "OosAtomService.h"
#import <oos4ios/Oos4ios.h>
#import <oos4ios/Auth/IOosAuthenticationHandler.h>
#import "../../GData/Common/Optional/Introspection/GDataAtomCategoryGroup.h"
#import "../../GData/Common/BaseClasses/GDataObject.h"


@interface AtomRequest : NSObject {
    GDataObject *objectToPost;
    NSURL *queryURL;
    id<IOosAtomServiceCallback> callback;
    NSString *httpMethod;
    GDataServiceTicketBase *ticket;
}

@property (nonatomic, retain) GDataObject *objectToPost;
@property (nonatomic, retain) NSURL *queryURL;
@property (nonatomic, retain) id<IOosAtomServiceCallback> callback;
@property (nonatomic, retain) NSString *httpMethod;
@property (nonatomic, retain) GDataServiceTicketBase *ticket;

@end

@implementation AtomRequest

@synthesize objectToPost;
@synthesize queryURL;
@synthesize callback;
@synthesize httpMethod;
@synthesize ticket;

@end

@implementation OosAtomService

#ifdef OOS_TESTING
@synthesize testURL = mTestURL;

- (BOOL) hasTestingURL
{
	return mTestURL != nil;
}
#endif

@synthesize query = mQuery;
@synthesize feedClass = mFeedClass;
@synthesize entryClass = mEntryClass;

+ (OosAtomService *) atomService
{
	return [[[OosAtomService alloc] init] autorelease];
}

+ (NSString *) defaultServiceVersion
{
	return @"2.0";
}

enum {
    kInvocationObjectURLIndex = 2,
    kInvocationObjectClassIndex,
    kInvocationObjectToPostIndex,
    kInvocationObjectETagIndex,
    kInvocationHTTPMethodIndex,
    kInvocationDelegateIndex,
    kInvocationFinishedSelectorIndex,
    kInvocationCompletionHandlerIndex,
    kInvocationRetryInvocationValueIndex,
    kInvocationTicketIndex
};

- (void) fetchNextRequest
{
    AtomRequest *request = nil;
    //@synchronized(requestQueue) {
        if (requestQueue && requestQueue.count > 0) {
            request = [requestQueue objectAtIndex: 0];
            [[request retain] autorelease];
            [requestQueue removeObject:request];
        }
    //}
    
    if (request) {
        
        GDataObject *objectToPost = request.objectToPost;
        NSString *httpMethod = request.httpMethod;
        id<IOosAtomServiceCallback> callback = request.callback;
        GDataServiceTicketBase *ticket = request.ticket;
        
        SEL theSelector = @selector(fetchObjectWithURL:objectClass:objectToPost:ETag:httpMethod:delegate:didFinishSelector:completionHandler:retryInvocationValue:ticket:);
        
        if (objectToPost) {
            // be sure the postedObject is available to the callbacks even if we fail
            // in authenticating, before getting around to trying to upload it
            [ticket setPostedObject:objectToPost];
        }
        
        self.query.appToken = [PrivateConstants appKey];
        
        NSURL * objectURL = [self.query URL];
        Class objectClass = nil;
        
        if (callback.feedParser) {
            objectClass = self.feedClass;
        } else if (callback.entryParser) {
            objectClass = self.entryClass;
        } else if (callback.categoryParser) {
            objectClass = [GDataAtomCategoryGroup class];
        }else {
            [NSException raise: @"no feedClass nor entryClass set" 
                        format: @"Missing feed/entryClass at: -read"];
        }
        
        SEL finishedSelector = @selector(ticket:finishedWithObject:error:);
        
        id nilValue = nil;
        
        NSMethodSignature *signature = [self methodSignatureForSelector:theSelector];
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        [invocation setSelector:theSelector];
        [invocation setTarget:self];
        [invocation setArgument:&objectURL        
                        atIndex:kInvocationObjectURLIndex];
        [invocation setArgument:&objectClass        
                        atIndex:kInvocationObjectClassIndex];
        [invocation setArgument:&objectToPost       
                        atIndex:kInvocationObjectToPostIndex];
        [invocation setArgument:&nilValue           
                        atIndex:kInvocationObjectETagIndex];
        [invocation setArgument:&httpMethod         
                        atIndex:kInvocationHTTPMethodIndex];
        [invocation setArgument:&self               
                        atIndex:kInvocationDelegateIndex];
        [invocation setArgument:&finishedSelector   
                        atIndex:kInvocationFinishedSelectorIndex];
        [invocation setArgument:&nilValue           
                        atIndex:kInvocationCompletionHandlerIndex];
        [invocation setArgument:&ticket
                        atIndex:kInvocationTicketIndex];
        
        NSValue *noRetryInvocation = nil;
        [invocation setArgument:&noRetryInvocation 
                        atIndex:kInvocationRetryInvocationValueIndex];
        
        [invocation retainArguments];
        
        NSValue *invocationValue = [NSValue valueWithNonretainedObject:invocation];
        
        [invocation setArgument:&invocationValue
                        atIndex:kInvocationRetryInvocationValueIndex];
        [invocation invoke];
        [invocation getReturnValue:&ticket];
        
        [OosAtomService registerTicket:ticket withCallback:callback];
    }
}

- (GDataServiceTicketBase *)fetchOosObject: (GDataObject *)objectToPost
                                httpMethod: (NSString *)httpMethod
                                  callback: (id<IOosAtomServiceCallback>) callback
{
    
    GDataServiceTicketBase *ticket = [GDataServiceTicketBase ticketForService:self];
    
    AtomRequest *request = [[AtomRequest new] autorelease];
    request.objectToPost = objectToPost;
    request.httpMethod = httpMethod;
    request.callback = callback;
    request.queryURL = [self.query URL];
    request.ticket = ticket;
    //@synchronized(requestQueue) {
        if (!requestQueue) {
            requestQueue = [[NSMutableArray arrayWithObject:request] retain];
        } else {
            [requestQueue addObject: request];
        }
        if ([requestQueue count] > 0) {
            [self fetchNextRequest];
        }
    //}
    
    return ticket;
}

- (BOOL)shouldCacheDatedData
{
    return NO;
}

- (GDataServiceTicketBase *) create:(id)object 
                       withCallback:(id<IOosAtomServiceCallback>)callback
{
    NSLog(@"creating: %@", [self.query URL], nil);
    if (!object || [object isKindOfClass:[GDataObject class]]) {
        return [self fetchOosObject:object 
                         httpMethod:@"POST" 
                           callback:callback];
    } else {
        [NSException raise:@"11870" format:@"trying to post a non GDataObject"];
        return nil;
    }
	
}

- (GDataServiceTicketBase *) update: (OosEntry *)entry 
                       withCallback: (id<IOosAtomServiceCallback>) callback
{
	NSLog(@"updating: %@", [self.query URL], nil);
	return [self fetchOosObject:entry 
                     httpMethod:@"PUT" 
                       callback:callback];
}

- (GDataServiceTicketBase *) read: (id<IOosAtomServiceCallback>) callback
{
    
    NSLog(@"reading: %@", [self.query URL], nil);
    
#ifdef OOS_TESTING
	if ([self hasTestingURL]) {
		NSURL *url = [self testURL];
        self.query = [OosQuery query:url];
	} else {
		[NSException raise:@"No test URL set" 
					format:@"A test URL must be set for testing"];
		return nil; 
	}
#endif
	return [self fetchOosObject: nil 
                     httpMethod: nil 
                       callback: callback];
}

- (GDataServiceTicketBase *) deleteEntry:(OosEntry *) entry 
                            withCallback: (id<IOosAtomServiceCallback>) callback
{
	NSLog(@"deleting: %@", [self.query URL], nil);
	return [self fetchOosObject:entry 
                     httpMethod:@"DELETE" 
                       callback:callback];
}

static NSMutableDictionary *mTicketCache;

+ (void) report:(GDataServiceTicketBase *)ticket 
      withError:(NSError *)error
{
	id <IOosAtomServiceCallback> callback;
    callback = [OosAtomService callbackForTicket:ticket];
	if (callback) {
		[callback problemWithFetch:error];
		[mTicketCache removeObjectForKey:ticket];
	}
}

+ (void) report:(GDataServiceTicketBase *)ticket 
     withObject:(id)object
{
	id <IOosAtomServiceCallback> callback;
    callback = [OosAtomService callbackForTicket:ticket];
	if (callback) {
        if ([object isKindOfClass:[OosFeed class]]) {
            [callback receivedFeed:object];
        } else if ([object isKindOfClass:[GDataAtomCategoryGroup class]]) {
            [callback receivedCategoryGroup:object];
        } else if([object isKindOfClass: [GDataEntryBase class]]) {
            [callback receivedEntry: object];
        } else if([object isKindOfClass: [NSNumber class]]) {
            int statusCode = [(NSNumber *)object intValue];
            if (statusCode >= 200 && statusCode < 300) {
                if ([callback respondsToSelector: @selector(receivedCorrect)]) {
                    [callback receivedCorrect];
                }
            } else {
                NSLog(@"Something should be done with %@", object);
            }
        }
		[self removeTicket:ticket];
	}
}

- (void)ticket:(GDataServiceTicketBase *)ticket 
finishedWithObject:(GDataObject *)object 
		 error:(NSError *)error {
    [self fetchNextRequest];
	if (error) {
		[OosAtomService report:ticket withError:error];
	} else if (object) {
        [OosAtomService report:ticket withObject: object];
	} else if ([ticket statusCode] > 0) {
        [OosAtomService report:ticket 
                    withObject: [NSNumber numberWithInt:[ticket statusCode]]];
    } else {
		NSLog(@"Something went bananas in ticket:finishedWithObject:");
	}
    
}

+ (id<IOosAtomServiceCallback>) callbackForTicket: (GDataServiceTicketBase *) ticket
{
	NSValue *key = [NSValue valueWithPointer:ticket];
	if(mTicketCache) {
		return (id <IOosAtomServiceCallback>)[mTicketCache objectForKey:key];
	} else {
		return nil;
	}
}

+ (void) registerTicket:(GDataServiceTicketBase *)ticket 
		   withCallback:(id <IOosAtomServiceCallback>)callback
{
	
	NSLog(@"in register ticket: %@ withCallback: %@", ticket, callback);
	NSValue *key = [NSValue valueWithPointer:ticket];
	if (mTicketCache) {
		[mTicketCache setObject:callback forKey:key];
	} else {
		mTicketCache = [[NSMutableDictionary alloc] 
						initWithObjectsAndKeys:callback, key, nil];
	}
}

+ (void) removeTicket:(GDataServiceTicketBase *)ticket
{
	NSValue *key = [NSValue valueWithPointer:ticket];
	[ticket cancelTicket];
	[mTicketCache removeObjectForKey:key];
}

/**
 Overriden to apply signing if it is required.
 */
- (NSMutableURLRequest *) requestForURL:(NSURL *)url 
								   ETag:(NSString *)etag 
							 httpMethod:(NSString *)httpMethod 
								 ticket:(GDataServiceTicketBase *)ticket
{
	NSMutableURLRequest *request = [super requestForURL:url
												   ETag:etag 
											 httpMethod:httpMethod 
												 ticket:ticket];
    
    id<IOosAuthenticationHandler> handler;
    handler = [Oos4ios instance].authenticationHandler;
    if (![handler isAnonymous]) {
        [handler authorizeRequest: request];
    }
    return request;
}

/**
 Overriden to remove headers of the request if there is no object
 */
- (NSMutableURLRequest *)objectRequestForURL:(NSURL *)url 
                                      object:(GDataObject *)object 
                                        ETag:(NSString *)etag 
                                  httpMethod:(NSString *)httpMethod 
                                      ticket:(GDataServiceTicketBase *)ticket
{
    NSMutableURLRequest *request = [super objectRequestForURL:url 
                                                       object:object 
                                                         ETag:etag 
                                                   httpMethod:httpMethod 
                                                       ticket:ticket];
    if (!object) {
        [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
    }
    return request;
}

-(void)dealloc
{
    [mQuery release];
    [mFeedClass release];
    [mEntryClass release];
    [requestQueue release];
    [super dealloc];
}

@end


